use super::{Notify, FULL, PARTIAL};
use crate::Error;
use core::mem::{self, MaybeUninit};
use core::ptr;
use core::time::Duration;

#[repr(C)]
pub struct EventFd {
    fd: i32,
}

impl Default for EventFd {
    fn default() -> Self {
        let fd = unsafe { libc::eventfd(0, libc::EFD_NONBLOCK | libc::EFD_CLOEXEC) };
        assert!(fd > -1, "EventFd::default {}", Error::last_error());
        Self { fd }
    }
}

impl EventFd {
    fn write(&self, val: u64) -> isize {
        unsafe {
            libc::write(
                self.fd,
                ptr::addr_of!(val).cast::<libc::c_void>(),
                mem::size_of_val(&val),
            )
        }
    }

    fn read(&self) -> u64 {
        let mut val = 0_u64;
        unsafe {
            libc::read(
                self.fd,
                ptr::addr_of_mut!(val).cast::<libc::c_void>(),
                mem::size_of_val(&val),
            );
        }
        val
    }

    #[cfg(any(target_os = "linux", target_os = "android"))]
    fn wait(&self, read: bool, timeout: Option<Duration>) -> Option<Duration> {
        let mut tm = libc::timeval {
            tv_sec: 0,
            tv_usec: 0,
        };
        let mut set = MaybeUninit::<libc::fd_set>::zeroed();
        unsafe {
            libc::FD_SET(self.fd, set.as_mut_ptr());
        }
        let null_set = ptr::null_mut::<libc::fd_set>();
        let tm_ptr = if let Some(timeout) = timeout {
            tm.tv_sec = timeout.as_secs() as libc::time_t;
            tm.tv_usec = timeout.subsec_micros() as libc::suseconds_t;
            ptr::addr_of_mut!(tm)
        } else {
            ptr::null_mut::<libc::timeval>()
        };

        let (rd_set, wr_set) = if read {
            (set.as_mut_ptr(), null_set)
        } else {
            (null_set, set.as_mut_ptr())
        };
        unsafe { libc::select(self.fd + 1, rd_set, wr_set, null_set, tm_ptr) };
        if tm_ptr.is_null() {
            None
        } else {
            Some(Duration::new(tm.tv_sec as u64, (tm.tv_usec * 1000) as u32))
        }
    }
}

impl Drop for EventFd {
    fn drop(&mut self) {
        unsafe { libc::close(self.fd) };
    }
}

impl Notify for EventFd {
    fn notify(&self, from: u8, to: u8) {
        assert!(from != to);
        if from == PARTIAL {
            if to == FULL {
                self.write(u64::MAX >> 1);
            } else {
                self.read();
            }
        } else if from == FULL {
            self.read();
            if to == PARTIAL {
                self.write(u64::MAX >> 1);
            }
        } else if to == PARTIAL {
            self.write(u64::MAX >> 1);
        } else {
            self.write(u64::MAX - 1);
        }
    }

    fn wait_read(&self, timeout: Option<Duration>) -> Option<Duration> {
        self.wait(true, timeout)
    }

    fn wait_write(&self, timeout: Option<Duration>) -> Option<Duration> {
        self.wait(false, timeout)
    }

    fn read_fd_event(&self) -> (i32, bool) {
        (self.fd, true)
    }

    fn write_fd_event(&self) -> (i32, bool) {
        (self.fd, true)
    }
}
